// ------------------------------------------------------------------------------
// 
// Copyright (c) 2007-2008 Swampware, Inc.
// 
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
// 
// ------------------------------------------------------------------------------
using System;

namespace Water.Reflection
{
	/// <summary>
	/// Evaluates water code with added support for .NET features such 
	/// as dot-notation.
	/// </summary>
	public class ReflectionEvaluator : Water.Evaluator
	{

		public ReflectionEvaluator()
		{
		}

		public override object EvaluateIdentifier(Water.Identifier identifier, Water.Environment environment)
		{
			string name = identifier.Value;
			object value = environment.Identify(name);
			if(value != null)
			{
				//Exact match
				return value;
			}

			//Break it down.
			System.Text.StringBuilder tracker = new System.Text.StringBuilder();
			System.IO.TextReader reader = new System.IO.StringReader(name);
			string segment = GetSegment(reader);
			tracker.Append(segment);
			value = environment.Identify(segment);
			if(value == null)
			{
				return null;
			}
			segment = "";
			while(reader.Peek() != -1)
			{
				char ch = (char)reader.Read();
				if(ch == '[')
				{
					value = ApplyIndexer(GetIndexer(reader), value, environment);
					segment = "";
				}
				else if(ch == '.')
				{
					segment += GetSegment(reader);
					if(IsProperty(value, segment))
					{
						tracker.Append(".");
						tracker.Append(segment);
						value = ApplyProperty(segment, value);
						segment = "";
					}
					else if(IsMethod(value, segment))
					{
						tracker.Append(".");
						tracker.Append(segment);
						if(reader.Peek() != -1)
						{
							return null;
						}
						return new Water.Reflection.Method(value, segment);
					}
					else
					{
						segment += ".";
					}
				}
				if(value == null)
				{
					return null;
				}
			}
			if(segment.Length > 0)
			{
				return null;
			}
			return value;
		}

		private string GetSegment(System.IO.TextReader reader)
		{
			System.Text.StringBuilder stringBuilder = new System.Text.StringBuilder();
			while(reader.Peek() != -1)
			{
				char ch = (char)reader.Peek();
				if(ch == '[')
				{
					break;
				}
				else if(ch == '.')
				{
					break;
				}
				else
				{
					stringBuilder.Append(ch);
					reader.Read();
				}
			}
			return stringBuilder.ToString();
		}

		private bool IsProperty(object value, string name)
		{
			if(value is System.Collections.IDictionary)
			{
				System.Collections.IDictionary dictionary = (System.Collections.IDictionary)value;
				return dictionary.Contains(name);
			}

			foreach(System.ComponentModel.PropertyDescriptor propertyDescriptor in System.ComponentModel.TypeDescriptor.GetProperties(value))
			{
				if(propertyDescriptor.Name.ToLower().Equals(name.ToLower()))
				{
					return true;
				}
			}

			System.Reflection.FieldInfo fieldInfo = value.GetType().GetField(name);
			if(fieldInfo != null)
			{
				return true;
			}

			return false;
		}

		private bool IsMethod(object value, string name)
		{
			System.Type type = value.GetType();
			foreach (System.Reflection.MethodInfo methodInfo in type.GetMethods(System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Static))
			{
				if(methodInfo.Name.ToLower().Equals(name.ToLower()))
				{
					return true;
				}
			}
			return false;
		}

		private object ApplyProperty(string name, object value)
		{
			if(value is System.Collections.IDictionary)
			{
				System.Collections.IDictionary dictionary = (System.Collections.IDictionary)value;
				return dictionary[name];
			}
			else
			{
				System.Reflection.FieldInfo fieldInfo = value.GetType().GetField(name);
				if(fieldInfo != null)
				{
					return fieldInfo.GetValue(value);
				}
				else
				{
					System.ComponentModel.PropertyDescriptor propertyDescriptor = GetProperty(value, name);
					if(propertyDescriptor == null)
					{
						throw new System.Exception("Unable to find property: " + name);
					}
					return propertyDescriptor.GetValue(value);
				}
			}
		}

		private System.ComponentModel.PropertyDescriptor GetProperty(object value, string name)
		{
			foreach(System.ComponentModel.PropertyDescriptor propertyDescriptor in System.ComponentModel.TypeDescriptor.GetProperties(value))
			{
				if(propertyDescriptor.Name.ToLower().Equals(name.ToLower()))
				{
					return propertyDescriptor;
				}
			}
			return null;
		}

		private object ApplyIndexer(string name, object value, Water.Environment environment)
		{
			object index = Evaluate(Water.Parser.QuickParse(name), environment);

			if(value is Water.Dictionary)
			{
				Water.Dictionary dictionary = (Water.Dictionary)value;

				if(name.StartsWith("\"") && name.EndsWith("\""))
				{
					string key = EvaluateString(name.Substring(1, name.Length - 2), environment);
					value = dictionary[key];
				}
				else
				{
					string key = (string)EvaluateIdentifier(new Water.Identifier(name), environment);
					value = dictionary[key];
				}
			}
			else if (value.GetType().IsArray)
			{
				// Array Indexer
				System.Reflection.MethodInfo methodInfo = value.GetType().GetMethod("GetValue", new System.Type[] { index.GetType() } );
				value = methodInfo.Invoke(value, new object[] { index } );
			}
			else
			{
				System.Reflection.PropertyInfo propertyInfo = value.GetType().GetProperty("Item", new System.Type[] { index.GetType() } );
				if(propertyInfo == null)
				{
					throw new Exception("Unable to apply indexer.");
				}

				if(IsInt(index))
				{
					value = propertyInfo.GetValue(value, new object[] { index } );
				}
				else if(name.StartsWith("\"") && name.EndsWith("\""))
				{
					string key = EvaluateString(name.Substring(1, name.Length - 2), environment);
					value = propertyInfo.GetValue(value, new object[] { key } );
				}
				else
				{
					string key = (string)EvaluateIdentifier(new Water.Identifier(name), environment);
					value = propertyInfo.GetValue(value, new object[] { key } );
				}
			}
			return value;
		}

		private string GetIndexer(System.IO.TextReader reader)
		{
			System.Text.StringBuilder stringBuilder = new System.Text.StringBuilder();
			char ch;
			while((ch = (char)reader.Read()) != ']')
			{
				stringBuilder.Append(ch);
			}
			return stringBuilder.ToString();
		}

		private bool IsInt(object value)
		{
			return (value is System.Int32);
		}

	}
}
